package sim;

import java.awt.Color;
import java.awt.event.*;
import java.util.ArrayList;
import javax.swing.*;

import comm.*;
import env.*;
import gfx.*;
import static sim.Constants.*;

/**
 * The core simulation routines.
 */
public class Sim {
	public static long totalEffort = 0;

	private static String config = "config_1_0";
	private static boolean logging = false;
	public static boolean drawTrace = true;
	private static int waitAfterDone = 50;

	public static int runs, delay, stepLimit;
	public static Color textColor;

	public static ArrayList<Obj> objects = new ArrayList<Obj>();
	public static ArrayList<TaskNode> tasks = new ArrayList<TaskNode>();
	public static ArrayList<Obj> a;
	public static Goal goal;

	public static long coordTime = 0, pushTime = 0;
	public static int  steps = 0;
	private static int robotCount = 0;
	private static int objCount = 0;

	public static int screenWidth, screenHeight;
	private static JFrame mainFrame = new JFrame();
	private static DrawPanel mainPanel = new DrawPanel();

	private static boolean pause = false;
	private static boolean exitPressed = false;

	//-0.12208515011681775,0.7078444469494239,0.20027745938271668,
	//0.8598901522952849,0.5967489306827667,-0.04470605378047299,-0.1836985294191601
	//  wEffort,  wPriority,  wDist,  wTurn,  wObstacles,  wCost,  wVelocity
	
//	public static double wEffort = -0.122, wPriority = 0.7078;
//	public static double wDist = 0.2, wTurn = 0.86, wCost = -0.044, wVelocity=-0.184;
//	public static double wObstacles=0.6;
	
	public static double /*wTime = 1,*/ wEffort = 1, wPriority = 1;
	public static double wDist = 1, wTurn = 0.2, wCost = 1, wVelocity=1;
	public static double wObstacles=1;
	public static double nDist, nEffort;

	

	private static boolean init() {
		try {
			resetSimulation();
			objects.clear(); //TODO Changed
			ConfigReader.read(config);
			Potential.reset(); 
			// had to move this here instead of resetSimulation() because
			// the potential should be initialized first before resetting
			// NOTE: This may be redundant since ConfigReader.read() might
			// also reset the potential
			int max = 1, temp;
			for (Obj e : objects)
				if (e instanceof PushableObj) {
					temp = e.getWidth() * e.getHeight() * e.getHeight();
					if (temp > max)
						max = temp;
				}
			setNormConstants(screenWidth, screenHeight, max);
			createWindow();
			mainPanel.addBackground(goal);
			return true;
		} catch (Exception e) {
			System.err.println(e);
			e.printStackTrace();
			return false;
		}
	}

	private static void createWindow() {
		mainPanel = new DrawPanel();
		mainFrame.getContentPane().add(mainPanel);
		mainFrame.setSize(mainPanel.getWidth(), mainPanel.getHeight());
		mainFrame.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				exitPressed = true;
			}
		});
		mainFrame.addKeyListener(new KeyListener() {
			public void keyPressed(KeyEvent e) {
			}

			public void keyReleased(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_P)
					pause = !pause;
			}

			public void keyTyped(KeyEvent e) {
			}
		});
		mainFrame.setVisible(true);
	}

	private static void resetSimulation() {
		totalEffort = 0;
		coordTime = pushTime = steps = 0;
		a = objects;
		tasks.clear();
		Whiteboard.tasks=tasks; //TODO possible memory leak here?
		Whiteboard.seenObjs.clear();
		Whiteboard.unavailObjs.clear();
		//Potential.reset();
	}

	private static int run() {
		boolean done = false;
		int wait = waitAfterDone;

		while (!done || wait > 0) {
			if (exitPressed)
				return WINDOW_CLOSED;
			if (pause)
				continue;
			
				
			steps++;
		//	System.out.println("steps: "+steps++);
			mainPanel.drawObjects(a);
			/*if (steps%100 == 0){
				System.out.println(steps + " steps has been taken");
			}*/

			if (delay > 0)
				delay(delay);

			done = true;
			for (Obj e : a)
				if (e instanceof Robot)
					((Robot) e).execute(a);
			for (Obj e : a)
				if (e.isPushable()) {
					done = false;
					break;
				}

			if (done)
				wait--;
			else if (steps > stepLimit)
				return RUN_INCOMPLETE;
		}
		return steps;
	}

	private static void delay(int i) {
		try {
			Thread.sleep(i);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static TaskNode getTask(String s) {
		for (TaskNode t : tasks)
			if (t.task.getID().equals(s))
				return t;
		return null;
	}

	public static void addRobot(String id, int x, int y, int w, int h, int angle, int vmax,
			int pmax, int fmax, int cost, int cap, Color color, int drawType) {
		robotCount++;
		if (id == null)
			id = "r" + robotCount;
		objects.add(new Robot(id, x, y, w, h, angle, vmax, pmax, fmax,cost, cap, color, drawType));
	}

	public static void addObj(String id, int x, int y, int w, int h, double angle, int cap, int f1,
			int f2, Color color) {
		objCount++;
		if (id == null)
			id = "b" + objCount;
		PushableObj o = new PushableObj(id, x, y, w, h, angle, cap, f1, f2, color);
		o.setReq(1);
		objects.add(o);
		TaskNode t = new TaskNode((PushableObj) o);
		t.priority = DEFAULT_PRIORITY;
		t.bias = DEFAULT_BIAS;
		tasks.add(t);
		
	}

	public static void addObstacle(int x, int y, int r) {
		objects.add(new Obstacle(x, y, r));
	}

	public static double rand() {
		return Math.random();
	}

	public static void print(Object o) {
		System.out.println(o);
	}

	public static void err(Object o) {
		System.err.println(o);
	}

	// For external calls
	public static int runOnce(String filename,/* double wTime,*/ double wEffort, double wPriority, double wDist, double wTurn, double wObstacles, double wCost, double wVelocity) {
		config = filename;
		if (!init())
			return -1;

		mainFrame.setVisible(false);
		delay = 0;
		waitAfterDone = 0;
		logging = false;
		drawTrace = false;

		setWeights(/*wTime,*/ wEffort, wPriority);
		setFineWeights(wDist, wTurn);
		setObstacleWeight(wObstacles);
		setCostWeight(wCost);
		setVelocityWeight(wVelocity);

		System.out.print("Started... ");
		int result = run(), ret;
		if (result == RUN_INCOMPLETE) {
			err("run incomplete! file: "+filename);
			ret = -2;
		} else {
			print("run completed in " + result + " steps.");
			ret = result;
		}
		mainFrame.dispose();
		return ret;
	}

	/*
	public static long runOnce(String filename, double wDist, double wTurn) {
		config = filename;
		if (!init())
			return -1;

		mainFrame.setVisible(false);
		delay = 0;
		waitAfterDone = 0;
		logging = false;
		drawTrace = false;

		// Fine weights
		setFineWeights(wDist, wTurn);

		System.out.print("Started... ");
		long result = run(), ret;
		if (result == RUN_INCOMPLETE) {
			err("run incomplete!\n");
			ret = Integer.MAX_VALUE;
		} else {
			print("run completed in " + result + " steps.");
			ret = result;
		}
		mainFrame.dispose();
		return ret;
	}
	*/

	public static void setWeights(/*double time,*/ double effort, double priority) {
		//wTime = time;
		wEffort = effort;
		wPriority = priority;
	}

	public static void setFineWeights(double distance, double turn) {
		wDist = distance;
		wTurn = turn;
	}
	
	public static void setObstacleWeight(double obs) {
		wObstacles = obs;
	}
	
	public static void setCostWeight(double cost) {
		wCost = cost;
	}
	
	public static void setVelocityWeight(double vel) { wVelocity = vel; }

	public static void setNormConstants(int wScreen, int hScreen, int e) {
		nDist = Math.sqrt(wScreen * wScreen + hScreen * hScreen);
		nEffort = e;
	}
	
	public static void main(String[] args) {
		if (!init()) {
			err("Failed to read config file \"" + config + "\".");
			return;
		}
		if (logging)
			Logger.init();

		Logger.logBeginSim();
		long result;
		runs = 3;
		for (int i = 1; i <= runs; i++) {
			mainFrame.setTitle("Run " + i + " of " + runs);
			
			result = run();
			if (result == WINDOW_CLOSED) {
				Logger.logExit();
				mainFrame.dispose();
				return;
			} else if (result == RUN_INCOMPLETE)
				Logger.logRunIncomplete(i, runs);
			else {
				Logger.logRunComplete(i, runs, result);
				print(totalEffort);
			}
			
			resetSimulation();
			//init();

		}
		Logger.logEndSim();
		Logger.end();
		mainFrame.dispose();
	}
}
